#include "SceneManager.h"
#include <time.h>


int SceneManager::crt_render_mode;
double SceneManager::cyl_height = 3;
double SceneManager::cyl_ray = 1;
double SceneManager::sph_ray = 3;
MCamera * SceneManager::camera = NULL;
list<Shape*> SceneManager::list_object = list<Shape *>();
vector<LITE*> SceneManager::list_light = vector<LITE*>();
MVertex SceneManager::click_to;
MVertex SceneManager::click_from;
float SceneManager::window_x = 700;
float SceneManager::window_y = 700;
float SceneManager::mouse_x = window_x/2;
float SceneManager::mouse_y = window_y/2;
bool SceneManager::bJump = false;
MCamera *SceneManager::cam_beforeJump = NULL;
float SceneManager::jump_speed;
//some constant
static const float CAM_TAMOUNT = 1;
static const float CAM_RAMOUNT = 1;
#define MOUSE_LOCATION 0x01
#define CAMERA_JUMP 0x02
#define CAMERA_JUMP_INIT_SPEED 0.2
template <class T>
void DELETEO(T *x) {
	if(x) 
		delete x;
}

SceneManager::~SceneManager(void)
{

}

void SceneManager::my_setup() 
{
	crt_render_mode = GL_LINE_LOOP;
	cam_beforeJump = new MCamera(*camera);
	return;
}

int SceneManager::my_glmain(int argc, char **argv) {
	if(argc <= 1) {
		cerr<<"Please Enter a filename!"<<endl;
		return -1;
	}
	if(read_spec(argv[1]) == -1) {
		return -1;
	}
	glutInit( &argc, argv ) ;

	/* we define these setup procedures */
	my_glutsetup() ;  
	my_glsetup() ;
	my_setup();

	/* go into the main event loop */
	glutMainLoop() ;

	return(0) ;
}


int SceneManager::read_spec(string path) {
	ifstream fin(path);
	if(fin.is_open()) {
		string tmp;
		while(fin.good()) {
			getline(fin,tmp);
			if(tmp=="") 
				continue;
			if(tmp[0] != '#') {
				vector<string> tmpv;
				istringstream stm(tmp);
				while(getline(stm,tmp,' ')) {
					tmpv.push_back(tmp);
				}
				if(tmpv[0] == "l") {	//light
					LITE *newLight = new LITE();
					newLight->amb[0] = atof(tmpv[1].data()+1);
					newLight->amb[1] = atof(tmpv[2].data());
					newLight->amb[2] = atof(tmpv[3].data());
					newLight->amb[3] = 1;
					newLight->diff[0] = atof(tmpv[4].data()+1);
					newLight->diff[1] = atof(tmpv[5].data());
					newLight->diff[2] = atof(tmpv[6].data());
					newLight->diff[3] = 1;
					newLight->spec[0] = atof(tmpv[7].data()+1);
					newLight->spec[1] = atof(tmpv[8].data());
					newLight->spec[2] = atof(tmpv[9].data());
					newLight->spec[3] = 1;
					newLight->pos[0] = atof(tmpv[10].data()+1);
					newLight->pos[1] = atof(tmpv[11].data());
					newLight->pos[2] = atof(tmpv[12].data());
					newLight->pos[3] = 1;
					if(tmpv.size() > 14) {//dir
						newLight->dir[0] = atof(tmpv[14].data()+1);
						newLight->dir[1] = atof(tmpv[15].data());
						newLight->dir[2] = atof(tmpv[16].data());
						newLight->dir[3] = 1;
					}
					if(tmpv.size() > 18) {
						newLight->angle = atof(tmpv[18].data()+1);
					}
					list_light.push_back(newLight);
				}
				else if(tmpv[0] == "c") {	//camera
					MVertex cam_pos(atof(tmpv[1].data()+1), atof(tmpv[2].data()), atof(tmpv[3].data()));
					MVertex cam_look(atof(tmpv[5].data()+1), atof(tmpv[6].data()), atof(tmpv[7].data()));
					MVertex cam_up(atof(tmpv[9].data()+1), atof(tmpv[10].data()), atof(tmpv[11].data()));
					//create camera
					camera = new MCamera(cam_pos,cam_look,cam_up);
				}
				else {	//object
					int shapetype = atoi(tmpv[0].data());
					Shape *newShape = NULL;
					switch (shapetype) {
					case 1: //cube 
						newShape = MCube::makeCube(1,4);
						break;
					case 2: //house
						newShape = new MHouse();
						break;
					case 3:	//sphere
						newShape = MSphere::makeSphere(0.5,8,8);
						break;
					case 4:	//cylinder
						newShape = MCylinder::makeCylinder(0.5,1,8,8,1,0,-0.5,0);
						break;
					case 5: //cones
						newShape = MCone::makeCone(0.5,1,2,10);
						break;
					case 6:	//donut
						newShape = MTorus::makeTorus(1.0,0.2,16,16);
						break;
					default:
						cout<<"Some unknow shape"<<endl;
						break;
					}
					if(newShape) {
						newShape->mat_shiny = atof(tmpv[13].data()+1);
						newShape->mat_emission = MVertex(atof(tmpv[14].data()+1), atof(tmpv[15].data()), atof(tmpv[16].data()));
						newShape->mat_amb = MVertex(atof(tmpv[18].data()+1), atof(tmpv[19].data()), atof(tmpv[20].data()));
						newShape->mat_diffuse = MVertex(atof(tmpv[22].data()+1), atof(tmpv[23].data()), atof(tmpv[24].data()));
						newShape->mat_specular = MVertex(atof(tmpv[26].data()+1), atof(tmpv[27].data()), atof(tmpv[28].data()));
						newShape->mt = MVertex(atof(tmpv[1].data()+1), atof(tmpv[2].data()), atof(tmpv[3].data()));
						newShape->ms = MVertex(atof(tmpv[5].data()+1), atof(tmpv[6].data()), atof(tmpv[7].data()));
						newShape->mr = MVertex(atof(tmpv[9].data()+1)/180*PI, atof(tmpv[10].data())/180*PI, atof(tmpv[11].data())/180*PI);
						newShape->scale(atof(tmpv[5].data()+1), atof(tmpv[6].data()), atof(tmpv[7].data()));
						newShape->rotate(atof(tmpv[9].data()+1), 1, 0, 0);
						newShape->rotate(atof(tmpv[10].data()), 0, 1, 0);
						newShape->rotate(atof(tmpv[11].data()), 0, 0, 1);
						newShape->translate(atof(tmpv[1].data()+1), atof(tmpv[2].data()), atof(tmpv[3].data()));
					}
					list_object.push_back(newShape);
				}//end of object branch
			}//end of it's a non comment line
		}//end of each line
	}//end of file exist
	else {//can't find the file specified
		fprintf(stderr,"Can not open file: %s \n", path);
		return -1;
	}
	return 0;
}

void  SceneManager::my_glutsetup() {
	/* specify display mode -- here we ask for a double buffer and RGB coloring */
	/* NEW: tell display we care about depth now */
	glutInitDisplayMode (GLUT_DOUBLE |GLUT_RGB |GLUT_DEPTH);

	glutInitWindowSize(window_x,window_y);
	glutInitWindowPosition(100,100);
	glutCreateWindow("CS1566 Project 4");

	/*initialize callback functions */
	glutDisplayFunc( my_display );
	glutReshapeFunc( my_reshape ); 
	glutMouseFunc( my_mouse);
	glutKeyboardFunc(my_keyboard);
	glutKeyboardUpFunc(my_keyboardup);
	glutSpecialFunc(my_specialdown);
	glutSpecialUpFunc(my_specialup);
	glutPassiveMotionFunc(my_mouse_passivemotion);
	//glutTimerFunc( 10, my_timeout, MOUSE_LOCATION);
	//glutIdleFunc( my_idle );
	return ;
}

void  SceneManager::my_glsetup() {
	 // enable depth handling (z-buffer)
  glEnable(GL_DEPTH_TEST);

  // enable auto normalize
  glEnable(GL_NORMALIZE);

  // define the background color 
  glClearColor(0,0,0,1);

  glMatrixMode(GL_PROJECTION) ;
  glLoadIdentity() ;
  gluPerspective( 40, 1.0, 1, 200.0);
  glMatrixMode(GL_MODELVIEW) ;
  glLoadIdentity() ;  // init modelview to identity

  // toggle to smooth shading (instead of flat)
  glShadeModel(GL_SMOOTH); 
  //lighting_setup();
}

void SceneManager::lighting_setup () {
	int i;
	GLfloat globalAmb[]     = {.1, .1, .1, .1};

	// create flashlight
	GLfloat amb[] = {0.2, 0.2, 0.2, 1.0};
	GLfloat dif[] = {0.8, 0.8, 0.8, 1.0};
	GLfloat spec[] = {5.0, 5.0, 5.0, 1.0};

	//enable lighting
	glEnable(GL_LIGHTING);
	glEnable(GL_NORMALIZE);
	glEnable(GL_COLOR_MATERIAL);

	// reflective propoerites -- global ambiant light
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, globalAmb);

	// this was for the flashlights

	// glLightfv(GL_LIGHT0, GL_POSITION, my_cam.at);
	// glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, my_cam.dir);

	// glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
	// glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
	// glLightfv(GL_LIGHT0, GL_SPECULAR, spec);
	// glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 30.0);

	// glEnable(GL_LIGHT0);

	// setup properties of lighting

	for (i=0; i<list_light.size(); i++) {
		glEnable(GL_LIGHT0+i);
		glLightfv(GL_LIGHT0+i, GL_AMBIENT, list_light[i]->amb);
		glLightfv(GL_LIGHT0+i, GL_DIFFUSE, list_light[i]->diff);
		glLightfv(GL_LIGHT0+i, GL_SPECULAR, list_light[i]->spec);
		glLightfv(GL_LIGHT0+i, GL_POSITION, list_light[i]->pos);
		if ((list_light[i]->dir[0] > 0) ||  (list_light[i]->dir[1] > 0) ||  (list_light[i]->dir[2] > 0)) {
			glLightf(GL_LIGHT0+i, GL_SPOT_CUTOFF, list_light[i]->angle);
			glLightfv(GL_LIGHT0+i, GL_SPOT_DIRECTION, list_light[i]->dir);
		}
	}
}

void SceneManager::my_display()
{
	/* clear the buffer */
	/* NEW: now we have to clear depth as well */
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) ;

	glMatrixMode(GL_MODELVIEW) ;
	glLoadIdentity();
	camera->updateGluLookAt();

	//draw world axis
	static const float axisLen = 3;
	float thickness=5.0;
	glLineWidth(thickness);
	draw_line(MVertex(0,0,0),MVertex(axisLen,0,0),1);
	draw_line(MVertex(0,0,0),MVertex(0,axisLen,0),1);
	draw_line(MVertex(0,0,0),MVertex(0,0,axisLen),1);
	
	//draw objects
	glLineWidth(1.0);
	for(list<Shape*>::iterator it = list_object.begin(); it != list_object.end(); it++) {
		(*it)->drawShape(draw_triangle,draw_line);	
		
		//draw intersection point
		for(list<MVertex>::iterator it1 = (*it)->list_intersect.begin(); 
			it1 != (*it)->list_intersect.end(); it1++) {
			MSphere *tmp = MSphere::makeSphere(0.02,8,8,(*it1).x(), (*it1).y(), (*it1).z());
			tmp->icolor = 1;
			tmp->drawShape(draw_triangle);
			DELETEO(tmp);
		}
	}

	//draw ray
	glLineWidth(1.0);
	draw_line(click_from, click_to, 2);
	/* buffer is ready */
	glutSwapBuffers();

	return ;
}


void SceneManager::my_timeout(int id)
{
	switch (id) {
	case MOUSE_LOCATION:
		{
			int marginx = window_x/9;
			int marginy = window_y/9;
			if(mouse_x <= marginx) {//turn left around camera's own y axis
				camera->rotateAroundAxis(camera->camPos, camera->camPos+camera->topV, CAM_RAMOUNT/5);
				glutPostRedisplay();
			}
			else if(mouse_x >= window_x-marginx) { //turn right around camera's own y axis
				camera->rotateAroundAxis(camera->camPos, camera->camPos+camera->topV, -CAM_RAMOUNT/5);
				glutPostRedisplay();
			}
			if(mouse_y <= marginy) {//turn up
				camera->rotateAroundAxis(camera->camPos, camera->camPos+camera->rightV, CAM_RAMOUNT/5);
				glutPostRedisplay();
			}
			else if(mouse_y >= window_y-marginy) {
				camera->rotateAroundAxis(camera->camPos, camera->camPos+camera->rightV, -CAM_RAMOUNT/5);
				glutPostRedisplay();
			}
			glutTimerFunc( 10, my_timeout, MOUSE_LOCATION);
		}
		break;
	case CAMERA_JUMP:
		{
			if(jump_speed > -CAMERA_JUMP_INIT_SPEED) {
				static float acc = 0.98/100;
				jump_speed -= acc;
				camera->translate(0,jump_speed,0);
				glutTimerFunc( 10, my_timeout, CAMERA_JUMP);
				glutPostRedisplay();
			}
			else {
				camera->camPos = cam_beforeJump->camPos;
				camera->lookAt = cam_beforeJump->lookAt;
				camera->upV = cam_beforeJump->upV;
				glutPostRedisplay();
				bJump = false;
			}
		}
		break;
	}
}

void SceneManager::my_keyboardup(unsigned char key, int x, int y) {
	
}


void SceneManager::my_specialdown(int key, int x, int y) {
	switch(key) {
	case GLUT_KEY_LEFT: 
		{
			MVertex rnorm = (camera->lookAt - camera->camPos).CrossProduct(camera->upV).normalize();
			camera->lookAt = camera->lookAt - rnorm*(CAM_TAMOUNT/10);
			camera->camPos = camera->camPos - rnorm*(CAM_TAMOUNT/10);
			glutPostRedisplay();
		}
		break;
	case GLUT_KEY_RIGHT:
		{
			MVertex rnorm = (camera->lookAt - camera->camPos).CrossProduct(camera->upV).normalize();
			camera->lookAt = camera->lookAt + rnorm*(CAM_TAMOUNT/10);
			camera->camPos = camera->camPos + rnorm*(CAM_TAMOUNT/10);
			glutPostRedisplay();
		}
		break;
	case GLUT_KEY_UP:
		{
			MVertex normal = (camera->lookAt - camera->camPos).normalize();
			camera->lookAt = camera->lookAt + normal*(CAM_TAMOUNT/10);
			camera->camPos = camera->camPos + normal*(CAM_TAMOUNT/10);
			glutPostRedisplay();
		}
		break;
	case GLUT_KEY_DOWN:
		{
			MVertex normal = (camera->lookAt - camera->camPos).normalize();
			camera->lookAt = camera->lookAt - (normal*(CAM_TAMOUNT/10));
			camera->camPos = camera->camPos - (normal*(CAM_TAMOUNT/10));
			glutPostRedisplay();
		}
		break;
	}
}

void SceneManager::my_specialup(int key, int x, int y) {

}

void SceneManager::my_keyboard(unsigned char key, int x, int y)
{
	switch( key ) {
	case 'W':
	case 'w':
		camera->translate(0,0,-CAM_TAMOUNT);
		glutPostRedisplay();
		break;
	case 's':
	case 'S':
		camera->translate(0,0,CAM_TAMOUNT);
		glutPostRedisplay();
		break;
	case 'a':
	case 'A':
		camera->rotate(-CAM_RAMOUNT,0,1,0);
		glutPostRedisplay();
		break;
	case 'd':
	case 'D':
		camera->rotate(CAM_RAMOUNT,0,1,0);
		glutPostRedisplay();
		break;
	case 'q': 
	case 'Q':
		cleanupExit();
		break;
	case 'O':
	case 'o':
		glutPostRedisplay();
		break;
	case 'J':
	case 'j':
		camera_jump();
		break;
	default:
		cout<<"key "<<(int)(key)<<endl;
		break;
	}

	return ;
}

void SceneManager::my_idle(void) {
	glutPostRedisplay();
	return ;
}

void SceneManager::my_reshape(int w, int h) {
	/* define viewport -- x, y, (origin is at lower left corner) width, height */
	glViewport (0, 0, min(w,h), min(w,h));
	window_x = w;
	window_y = h;
	return;
}


void SceneManager::my_mouse(int b, int s, int x, int y) {
	switch( b ) {

	case GLUT_LEFT_BUTTON:
		if( s == GLUT_DOWN ) {
			trace_ray(x,y);
			glutPostRedisplay();
		}
		if( s == GLUT_UP ) {
		} 
		break ;

	case GLUT_RIGHT_BUTTON:
		if ( s == GLUT_DOWN ) {

		}
		if( s == GLUT_UP ) {
		}
		break ;
	}

	return ;
}

void SceneManager::my_mouse_passivemotion(int x, int y) {
	mouse_x = x;
	mouse_y = y;
}

void SceneManager::camera_jump() {
	if(!bJump) {
		bJump = true;
		cam_beforeJump->camPos = camera->camPos;
		cam_beforeJump->lookAt = camera->lookAt;
		cam_beforeJump->upV = camera->upV;

		jump_speed = CAMERA_JUMP_INIT_SPEED;
		glutTimerFunc( 10, my_timeout, CAMERA_JUMP);
	}
}

void SceneManager::trace_ray(float mousex, float mousey) {
	double modelViewMatrix[16];
	double projMatrix[16];
	int viewport[4];
	double clickPoint[3];
	//mousex = 2*mousex/window_x -1;
	//mousey = 2*(window_y - mousey)/window_y -1;
	// first we need to get the modelview matrix, the projection matrix, and the viewport
	glGetDoublev(GL_MODELVIEW_MATRIX, modelViewMatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
	glGetIntegerv(GL_VIEWPORT, viewport);
	
	mousey = viewport[3] - mousey;
	cout<<mousex<<", "<<mousey<<endl;
	// gluUnProject with a Z value of 1 will find the point on the far clipping plane
	// corresponding the the mouse click. This is not the same as the vector
	// representing the click.
	gluUnProject(mousex, mousey, 1.0, modelViewMatrix, projMatrix, viewport, &clickPoint[0], &clickPoint[1], &clickPoint[2]);
	click_to = MVertex(clickPoint[0],clickPoint[1],clickPoint[2]);
	gluUnProject(mousex, mousey, 0.0, modelViewMatrix, projMatrix, viewport, &clickPoint[0], &clickPoint[1], &clickPoint[2]);
	click_from = MVertex(clickPoint[0],clickPoint[1],clickPoint[2]);
	click_from = camera->camPos;
	//cout<<"from "<<click_from<<endl<<"to "<<click_to<<endl;

	for(list<Shape*>::iterator it = list_object.begin(); it!=list_object.end(); it++) {
		(*it)->intersect(click_from,click_to);
	}
}

void SceneManager::draw_triangle(const GLfloat vertices[][4], int iv1, int iv2, int iv3, int ic) {
	glBegin(crt_render_mode); 
	{
		glColor3fv(colors[ic]);
		/*note the explicit use of homogeneous coords below: glVertex4f*/
		glVertex4fv(vertices[iv1]);
		glVertex4fv(vertices[iv2]);
		glVertex4fv(vertices[iv3]);
	}
	glEnd();
}

void SceneManager::draw_triangle(const MVertex &p1,  const MVertex &p2, const MVertex &p3, int ic) {
	glBegin(crt_render_mode); 
	{
		glColor3fv(colors[ic]);
		/*note the explicit use of homogeneous coords below: glVertex4f*/
		glVertex4fv(p1.point);
		glVertex4fv(p2.point);
		glVertex4fv(p3.point);
	}
	glEnd();
}

void SceneManager::draw_line(const MVertex &p1,  const MVertex &p2,int ic) {
	glBegin(GL_LINES); 
	{
		glColor3fv(colors[ic]);
		glVertex4fv(p1.point);
		glVertex4fv(p2.point);
	}
	glEnd();
}

void SceneManager::draw_poly(const vector<MVertex> &v1, int ic) {
	glBegin(crt_render_mode); 
	{
		glColor3fv(colors[ic]);
		for(unsigned int i =0; i<v1.size();i++) {
			/*note the explicit use of homogeneous coords below: glVertex4f*/
			glVertex4fv(v1[i].point);
		}
	}
	glEnd();
}

void SceneManager::cleanupExit() {

	DELETEO(camera);
	exit(0);
}








